package com.wngbms;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.AsyncCallback.StatCallback;
import org.apache.zookeeper.KeeperException.Code;

import java.io.*;
import java.util.Arrays;

@Slf4j
public class Executor {

    boolean dead;
    private ZooKeeper zk;

    private String pathname;
    private String[] exec;
    private String znode;

    private StatCallback statCallback = new StatCallback() {
        private byte[] prevData;

        @Override
        public void processResult(int rc, String path, Object ctx, Stat stat) {
            boolean exists;
            switch (rc) {
                case Code.Ok:
                    exists = true;
                    break;
                case Code.NoNode:
                    exists = false;
                    break;
                case Code.SessionExpired:
                case Code.NoAuth:
                    dead = true;
                    listener.closing(rc);
                    return;
                default:
                    zk.exists(znode, true, this, null);
                    return;
            }

            byte[] b = null;
            if (exists) {
                try {
                    b = zk.getData(znode, false, null);
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return;
                }
            }

            if ((b == null && b != prevData) || (b != null && !Arrays.equals(prevData, b))) {
                listener.exists(b);
                prevData = b;
            }
        }
    };

    private DataMonitorListener listener = new DataMonitorListener() {
        private Process child;

        @Override
        public void exists(byte[] data) {
            if (data == null) {
                if (child != null) {
                    log.info("killing handle");
                    child.destroy();
                    try {
                        child.waitFor();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                child = null;
            }
            else {
                if (child != null) {
                    log.info("Stopping child");
                    child.destroy();
                    try {
                        child.waitFor();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(pathname);
                    fos.write(data);
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    log.info("starting child");
                    child = Runtime.getRuntime().exec(exec);
                    new StreamWriter(child.getInputStream(), System.out);
                    new StreamWriter(child.getErrorStream(), System.err);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void closing(int rc) {
            synchronized (Executor.this) {
                Executor.this.notifyAll();
            }
        }
    };

    private Watcher watcher = new Watcher() {

        @Override
        public void process(WatchedEvent event) {
            String path = event.getPath();

            if (event.getType() == Watcher.Event.EventType.None) {
                switch (event.getState()) {
                    case SyncConnected:
                        break;
                    case Expired:
                        dead = true;
                        listener.closing(KeeperException.Code.SessionExpired);
                        break;
                }
            } else {
                if (path != null && path.equals(znode)) {
                    zk.exists(znode, true, statCallback, null);
                }
            }
        }
    };

    private Runnable runnable = () -> {
        try {
            synchronized (Executor.this) {
                while (!Executor.this.dead) {
                    Executor.this.wait();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };

    public Executor() throws IOException {
        this.pathname = "D:/znode-data";
        this.znode = "/wngb/test";
        this.exec = new String[] {"notepad"};

        zk = new ZooKeeper("localhost:2181", 3000, watcher);
        zk.exists(znode, true, statCallback, null);

        runnable.run();
    }

    public static void main(String[] args) throws IOException {
        new Executor();
    }


}
